use anyhow::Result;
use entities::prelude::Task as TaskEntity;
use sea_orm::{
    ColumnTrait, DbConn, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect,
    QueryTrait,
};

use crate::models::{QueryReq, Task};

use super::TaskCol;

pub struct TaskRepo;

impl TaskRepo {
    pub async fn create(db: &DbConn, t: Task) -> Result<i32> {
        let model = t.into_active_model();

        let res = TaskEntity::insert(model).exec(db).await?;

        Ok(res.last_insert_id)
    }

    pub async fn query_active_tasks(db: &DbConn) -> Result<Vec<String>> {
        let list: Vec<String> = TaskEntity::find()
            .select_only()
            .column(TaskCol::Gid)
            .filter(TaskCol::Status.eq("active"))
            .into_tuple()
            .all(db)
            .await?;

        Ok(list)
    }

    pub async fn query_waiting_tasks(db: &DbConn, limit: u64) -> Result<Vec<Task>> {
        let models = TaskEntity::find()
            .filter(TaskCol::Status.eq("waiting"))
            .order_by_asc(TaskCol::Id)
            .limit(limit)
            .all(db)
            .await?;

        Ok(models.into_iter().map(Task::from_model).collect())
    }

    pub async fn query_seeding_tasks(db: &DbConn) -> Result<Vec<String>> {
        let list: Vec<String> = TaskEntity::find()
            .select_only()
            .column(TaskCol::Gid)
            .filter(TaskCol::Status.eq("seeding"))
            .into_tuple()
            .all(db)
            .await?;

        Ok(list)
    }

    pub async fn get(db: &DbConn, id: i32) -> Result<Option<Task>> {
        let model = TaskEntity::find_by_id(id).one(db).await?;

        Ok(model.map(Task::from_model))
    }

    pub async fn get_by_gid(db: &DbConn, gid: String) -> Result<Option<Task>> {
        let model = TaskEntity::find()
            .filter(TaskCol::Gid.eq(gid))
            .one(db)
            .await?;

        Ok(model.map(Task::from_model))
    }

    pub async fn update(db: &DbConn, t: Task) -> Result<()> {
        let model = t.into_update_model();

        TaskEntity::update(model).exec(db).await?;

        Ok(())
    }

    pub async fn delete(db: &DbConn, id: i32) -> Result<()> {
        TaskEntity::delete_by_id(id).exec(db).await?;

        Ok(())
    }

    pub async fn is_exist(db: &DbConn, gid: String) -> Result<bool> {
        let count = TaskEntity::find()
            .filter(TaskCol::Gid.eq(gid))
            .count(db)
            .await?;

        Ok(count > 0)
    }

    pub async fn get_list(db: &DbConn, req: QueryReq) -> Result<Vec<Task>> {
        let models = TaskEntity::find()
            .apply_if(req.status, |query, status| {
                query.filter(TaskCol::Status.eq(String::from(status)))
            })
            .apply_if(req.name, |query, name| {
                query.filter(TaskCol::Name.contains(name))
            })
            .order_by_desc(TaskCol::Id)
            .all(db)
            .await?;

        Ok(models.into_iter().map(Task::from_model).collect())
    }

    pub async fn update_status(db: &DbConn, t: Task) -> Result<()> {
        let model = t.into_update_status();

        TaskEntity::update(model).exec(db).await?;

        Ok(())
    }

    pub async fn get_list_by_gids(db: &DbConn, gids: Vec<String>) -> Result<Vec<Task>> {
        let models = TaskEntity::find()
            .filter(TaskCol::Gid.is_in(gids))
            .all(db)
            .await?;

        Ok(models.into_iter().map(Task::from_model).collect())
    }

    pub async fn is_exist_uri(db: &DbConn, uri: String) -> Result<bool> {
        let count = TaskEntity::find()
            .filter(TaskCol::Uri.eq(uri))
            .count(db)
            .await?;

        Ok(count > 0)
    }

    pub async fn update_gid(db: &DbConn, t: Task) -> Result<()> {
        let model = t.into_update_gid();

        TaskEntity::update(model).exec(db).await?;

        Ok(())
    }
}
